{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "1e174bb9",
   "metadata": {},
   "source": [
    "# AutoGluon Assistant - Quick Start\n",
    "\n",
    "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://github.com/autogluon/autogluon-assistant)\n",
    "[![Open In SageMaker Studio Lab](https://studiolab.sagemaker.aws/studiolab.svg)](https://github.com/autogluon/autogluon-assistant)\n",
    "\n",
    "(Links above are still WIP)\n",
    "\n",
    "In this tutorial, we will see how to use AutoGluon Assistant (AG-A) to solve machine learning problems **with zero line of code**. AG-A combines the power of AutoGluon's state-of-the-art AutoML capabilities with Large Language Models (LLMs) to automate the entire data science pipeline.\n",
    "\n",
    "We will cover:\n",
    "- Setting up AutoGluon Assistant\n",
    "- Preparing Your Data\n",
    "- Using AutoGluon Assistant (via Command Line Interface)\n",
    "- Using AutoGluon Assistant (through Python Programming)\n",
    "- Using AutoGluon Assistant (via Web UI)\n",
    "\n",
    "By the end of this tutorial, you'll be able to run your data with our highly accurate ML solutions using just natural language instructions. Let's get started with the installation!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f06e8e04",
   "metadata": {},
   "source": [
    "## Setting up AutoGluon Assistant\n",
    "Getting started with AutoGluon Assistant is straightforward. Let's install it directly using pip:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f4d1edc3d2f610f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "!pip install autogluon.assistant"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3ea8b014",
   "metadata": {},
   "source": [
    "*Warning: If you are using an MacOS, you may need to install libomp with*\n",
    "```bash\n",
    "brew install libomp\n",
    "pip install --upgrade lightgbm\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d4f6834",
   "metadata": {},
   "source": [
    "AutoGluon Assistant supports two LLM providers: AWS Bedrock (default) and OpenAI. You can configure with our provided tool:\n",
    "```bash\n",
    "wget https://raw.githubusercontent.com/autogluon/autogluon-assistant/refs/heads/main/tools/configure_llms.sh\n",
    "source ./configure_llms.sh\n",
    "```\n",
    "Or choose one of the following setups:\n",
    "```bash\n",
    "# Option A: AWS Bedrock (Recommended)\n",
    "export AWS_DEFAULT_REGION='<your-region>'\n",
    "export AWS_ACCESS_KEY_ID='<your-access-key>'\n",
    "export AWS_SECRET_ACCESS_KEY='<your-secret-key>'\n",
    "### OR ###\n",
    "# Option B: OpenAI\n",
    "export OPENAI_API_KEY='sk-...'\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6ec461b5",
   "metadata": {},
   "source": [
    "*Note: If using OpenAI, we recommend a paid API key rather than a free-tier account to avoid rate limiting issues.*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ea9e1754",
   "metadata": {},
   "source": [
    "Let's verify the installation by importing the package:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ce77f5dc",
   "metadata": {},
   "outputs": [],
   "source": [
    "import autogluon.assistant\n",
    "\n",
    "print(autogluon.assistant.__version__)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "052f7fec",
   "metadata": {},
   "source": [
    "\n",
    "Now that you have AutoGluon Assistant installed and configured, let's move on to preparing your data directory structure for your first ML project!"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e42b07bc64929c80",
   "metadata": {},
   "source": [
    "## Preparing Your Data"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f247a5c20c9be613",
   "metadata": {},
   "source": [
    "For this tutorial, we'll use a spaceship transportation dataset which is perfect for getting started with machine learning. The goal is to predict whether an item was transported based on various numerical and categorical features in the dataset. We sampled 1000 training and test examples from the original data. The sampled dataset make this tutorial run quickly, but AutoGluon Assistant can handle the full dataset if desired.\n",
    "\n",
    "Let's download the example data:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bfda6620a2f2637",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%bash\n",
    "wget https://automl-mm-bench.s3.us-east-1.amazonaws.com/aga/data/aga_sample_data.zip\n",
    "unzip aga_sample_data.zip"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c810125a2b8aa286",
   "metadata": {},
   "source": [
    "That's it! We now have (under `./toy_data`):\n",
    "\n",
    "- `train.csv`: Training data with labeled examples\n",
    "- `test.csv`: Test data for making predictions\n",
    "- `descriptions.txt`: A description of the dataset and task\n",
    "\n",
    "Let's take a quick look at our training data and description file:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "735d0a050b701f31",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "train_data = pd.read_csv(\"toy_data/train.csv\")\n",
    "train_data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cbe4200e",
   "metadata": {
    "code_font_size": "6pt"
   },
   "source": [
    "```\n",
    "  PassengerId HomePlanet CryoSleep  Destination   Age    VIP  RoomService  FoodCourt  ShoppingMall  Spa  VRDeck                Name  Transported Deck  Cabin_num Side\n",
    "0     5647_01     Europa      True  TRAPPIST-1e  35.0  False          0.0        NaN           0.0  0.0     NaN  Dyonevi Matoltuble         True    C      178.0    P\n",
    "1     4061_02       Mars      True  TRAPPIST-1e   0.0  False          0.0        0.0           0.0  0.0     0.0          Graw Kashe         True    F      766.0    S\n",
    "2     0691_03       Mars      True  TRAPPIST-1e  23.0  False          0.0        0.0           0.0  0.0     0.0          Moss Potte         True    E       49.0    S\n",
    "3     1094_01      Earth     False  TRAPPIST-1e  60.0    NaN        437.0        2.0           0.0  0.0   365.0   Carona Webstenson        False    F      224.0    P\n",
    "4     6394_01      Earth      True  TRAPPIST-1e  41.0  False          0.0        0.0           0.0  0.0     0.0   Pattie Lambleyoun         True    G     1037.0    S\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e4e31f39",
   "metadata": {},
   "outputs": [],
   "source": [
    "with open('toy_data/descriptions.txt', 'r') as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bd190e32",
   "metadata": {
    "code_font_size": "6pt"
   },
   "source": [
    "```\n",
    "You are solving this data science tasks of binary classification: \n",
    "The dataset presented here (the spaceship dataset) comprises a lot of features, including both numerical and categorical features. Some of the features are missing, with nan value. We have splitted the dataset into three parts of train, valid and test. Your task is to predict the Transported item, which is a binary label with True and False. The evaluation metric is the classification accuracy.\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec8a61ef4291bc39",
   "metadata": {},
   "source": [
    "## Using AutoGluon Assistant (via Command Line Interface)\n",
    "\n",
    "Now that we have our data ready, let's use AutoGluon Assistant to build our ML model. The simplest way to use AutoGluon Assistant is through the command line - no coding required! After installing the package, you can run it directly from your terminal:\n",
    "\n",
    "```bash\n",
    "aga run ./toy_data \\\n",
    "    --presets medium_quality    # (Optional) Choose prediction quality level:\n",
    "                                # Options: medium_quality, high_quality, best_quality (default)\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8adda2cc",
   "metadata": {
    "code_font_size": "6pt"
   },
   "source": [
    "```\n",
    "INFO:root:Starting AutoGluon-Assistant\n",
    "INFO:root:Presets: medium_quality\n",
    "INFO:root:Loading default config from: /media/deephome/autogluon-assistant/src/autogluon/assistant/configs/default.yaml\n",
    "INFO:root:Merging medium_quality config from: /media/deephome/autogluon-assistant/src/autogluon/assistant/configs/medium_quality.yaml\n",
    "INFO:root:Successfully loaded config\n",
    "🤖  Welcome to AutoGluon-Assistant \n",
    "Will use task config:\n",
    "{\n",
    "    'infer_eval_metric': True,\n",
    "    'detect_and_drop_id_column': False,\n",
    "    'task_preprocessors_timeout': 3600,\n",
    "    'time_limit': 600,\n",
    "    'save_artifacts': {'enabled': False, 'append_timestamp': True, 'path': './aga-artifacts'},\n",
    "    'feature_transformers': {\n",
    "        'enabled_models': [],\n",
    "        'models': {\n",
    "            'CAAFE': {\n",
    "                '_target_': 'autogluon.assistant.transformer.feature_transformers.caafe.CAAFETransformer',\n",
    "                'eval_model': 'lightgbm',\n",
    "                'llm_provider': '${llm.provider}',\n",
    "                'llm_model': '${llm.model}',\n",
    "                'num_iterations': 5,\n",
    "                'optimization_metric': 'roc'\n",
    "            },\n",
    "            'OpenFE': {'_target_': 'autogluon.assistant.transformer.feature_transformers.openfe.OpenFETransformer', 'n_jobs': 1, 'num_features_to_keep': 10},\n",
    "            'PretrainedEmbedding': {'_target_': 'autogluon.assistant.transformer.feature_transformers.scentenceFT.PretrainedEmbeddingTransformer', 'model_name': 'all-mpnet-base-v2'}\n",
    "        }\n",
    "    },\n",
    "    'autogluon': {'predictor_init_kwargs': {}, 'predictor_fit_kwargs': {'presets': 'medium_quality'}},\n",
    "    'llm': {'provider': 'bedrock', 'model': 'anthropic.claude-3-5-sonnet-20241022-v2:0', 'max_tokens': 512, 'proxy_url': None, 'temperature': 0, 'verbose': True}\n",
    "}\n",
    "Task path: /media/deephome/autogluon-assistant/toy_data_newest_backup\n",
    "Task loaded!\n",
    "TabularPredictionTask(name=toy_data_newest_backup, description=, 3 datasets)\n",
    "INFO:botocore.credentials:Found credentials from IAM Role: Bedrock_Access\n",
    "AGA is using model anthropic.claude-3-5-sonnet-20241022-v2:0 from Bedrock to assist you with the task.\n",
    "INFO:botocore.credentials:Found credentials from IAM Role: Bedrock_Access\n",
    "INFO:root:It took 0.16 seconds initializing components. Time remaining: 599.83/600.00\n",
    "Task understanding starts...\n",
    "description: data_description_file: You are solving this data science tasks of binary classification: \\nThe dataset presented here (the spaceship dataset) comprises a lot of features, including both numerical and categorical features. Some of the features are missing, with nan value. We have splitted the dataset into three parts of train, valid and test. Your task is to predict the Transported item, which is a binary label with True and False. The evaluation metric is the classification accuracy.\\n\n",
    "train_data: /media/deephome/autogluon-assistant/toy_data_newest_backup/train.csv\n",
    "Loaded data from: /media/deephome/autogluon-assistant/toy_data_newest_backup/train.csv | Columns = 16 / 16 | Rows = 1000 -> 1000\n",
    "test_data: /media/deephome/autogluon-assistant/toy_data_newest_backup/test.csv\n",
    "Loaded data from: /media/deephome/autogluon-assistant/toy_data_newest_backup/test.csv | Columns = 16 / 16 | Rows = 1000 -> 1000\n",
    "WARNING: Failed to identify the sample_submission_data of the task, it is set to None.\n",
    "label_column: Transported\n",
    "problem_type: binary\n",
    "eval_metric: accuracy\n",
    "Total number of prompt tokens: 1614\n",
    "Total number of completion tokens: 179\n",
    "Task understanding complete!\n",
    "Automatic feature generation is disabled. \n",
    "INFO:root:It took 17.31 seconds preprocessing task. Time remaining: 582.51/600.00\n",
    "Model training starts...\n",
    "Fitting AutoGluon TabularPredictor\n",
    "predictor_init_kwargs: {'learner_kwargs': {'ignored_columns': []}, 'label': 'Transported', 'problem_type': 'binary', 'eval_metric': 'accuracy'}\n",
    "predictor_fit_kwargs: {'presets': 'medium_quality'}\n",
    "No path specified. Models will be saved in: \"AutogluonModels/ag-20241119_214901\"\n",
    "Verbosity: 2 (Standard Logging)\n",
    "=================== System Info ===================\n",
    "AutoGluon Version:  1.1.1\n",
    "Python Version:     3.10.14\n",
    "Operating System:   Linux\n",
    "Platform Machine:   x86_64\n",
    "Platform Version:   #54~20.04.1-Ubuntu SMP Fri Oct 6 22:04:33 UTC 2023\n",
    "CPU Count:          96\n",
    "Memory Avail:       1024.83 GB / 1121.80 GB (91.4%)\n",
    "Disk Space Avail:   63.91 GB / 860.63 GB (7.4%)\n",
    "===================================================\n",
    "Presets specified: ['medium_quality']\n",
    "Beginning AutoGluon training ... Time limit = 583s\n",
    "AutoGluon will save models to \"AutogluonModels/ag-20241119_214901\"\n",
    "Train Data Rows:    1000\n",
    "Train Data Columns: 15\n",
    "Label Column:       Transported\n",
    "Problem Type:       binary\n",
    "Preprocessing data ...\n",
    "Selected class <--> label mapping:  class 1 = True, class 0 = False\n",
    "Using Feature Generators to preprocess the data ...\n",
    "Fitting AutoMLPipelineFeatureGenerator...\n",
    "        Available Memory:                    1049426.06 MB\n",
    "        Train Data (Original)  Memory Usage: 0.48 MB (0.0% of available memory)\n",
    "        Inferring data type of each feature based on column values. Set feature_metadata_in to manually specify special dtypes of the features.\n",
    "        Stage 1 Generators:\n",
    "                Fitting AsTypeFeatureGenerator...\n",
    "        Stage 2 Generators:\n",
    "                Fitting FillNaFeatureGenerator...\n",
    "        Stage 3 Generators:\n",
    "                Fitting IdentityFeatureGenerator...\n",
    "                Fitting CategoryFeatureGenerator...\n",
    "                        Fitting CategoryMemoryMinimizeFeatureGenerator...\n",
    "        Stage 4 Generators:\n",
    "                Fitting DropUniqueFeatureGenerator...\n",
    "        Stage 5 Generators:\n",
    "                Fitting DropDuplicatesFeatureGenerator...\n",
    "        Unused Original Features (Count: 1): ['PassengerId']\n",
    "                These features were not used to generate any of the output features. Add a feature generator compatible with these features to utilize them.\n",
    "                Features can also be unused if they carry very little information, such as being categorical but having almost entirely unique values or being duplicates of other features.\n",
    "                These features do not need to be present at inference time.\n",
    "                ('object', []) : 1 | ['PassengerId']\n",
    "        Types of features in original data (raw dtype, special dtypes):\n",
    "                ('float', [])  : 7 | ['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', ...]\n",
    "                ('object', []) : 7 | ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Name', ...]\n",
    "        Types of features in processed data (raw dtype, special dtypes):\n",
    "                ('category', []) : 7 | ['HomePlanet', 'CryoSleep', 'Destination', 'VIP', 'Name', ...]\n",
    "                ('float', [])    : 7 | ['Age', 'RoomService', 'FoodCourt', 'ShoppingMall', 'Spa', ...]\n",
    "        0.1s = Fit runtime\n",
    "        14 features in original data used to generate 14 features in processed data.\n",
    "        Train Data (Processed) Memory Usage: 0.06 MB (0.0% of available memory)\n",
    "Data preprocessing and feature engineering runtime = 0.09s ...\n",
    "AutoGluon will gauge predictive performance using evaluation metric: 'accuracy'\n",
    "        To change this, specify the eval_metric parameter of Predictor()\n",
    "Automatically generating train/validation split with holdout_frac=0.2, Train Rows: 800, Val Rows: 200\n",
    "User-specified model hyperparameters to be fit:\n",
    "{\n",
    "        'NN_TORCH': {},\n",
    "        'GBM': [{'extra_trees': True, 'ag_args': {'name_suffix': 'XT'}}, {}, 'GBMLarge'],\n",
    "        'CAT': {},\n",
    "        'XGB': {},\n",
    "        'FASTAI': {},\n",
    "        'RF': [{'criterion': 'gini', 'ag_args': {'name_suffix': 'Gini', 'problem_types': ['binary', 'multiclass']}}, {'criterion': 'entropy', 'ag_args': {'name_suffix': 'Entr', 'problem_types': ['binary', 'multiclass']}}, {'criterion': 'squared_error', 'ag_args': {'name_suffix': 'MSE', 'problem_types': ['regression', 'quantile']}}],\n",
    "        'XT': [{'criterion': 'gini', 'ag_args': {'name_suffix': 'Gini', 'problem_types': ['binary', 'multiclass']}}, {'criterion': 'entropy', 'ag_args': {'name_suffix': 'Entr', 'problem_types': ['binary', 'multiclass']}}, {'criterion': 'squared_error', 'ag_args': {'name_suffix': 'MSE', 'problem_types': ['regression', 'quantile']}}],\n",
    "        'KNN': [{'weights': 'uniform', 'ag_args': {'name_suffix': 'Unif'}}, {'weights': 'distance', 'ag_args': {'name_suffix': 'Dist'}}],\n",
    "}\n",
    "Fitting 13 L1 models ...\n",
    "Fitting model: KNeighborsUnif ... Training model for up to 582.42s of the 582.42s of remaining time.\n",
    "        0.805    = Validation score   (accuracy)\n",
    "        0.04s    = Training   runtime\n",
    "        0.04s    = Validation runtime\n",
    "Fitting model: KNeighborsDist ... Training model for up to 582.34s of the 582.33s of remaining time.\n",
    "        0.79     = Validation score   (accuracy)\n",
    "        0.03s    = Training   runtime\n",
    "        0.04s    = Validation runtime\n",
    "Fitting model: LightGBMXT ... Training model for up to 582.27s of the 582.27s of remaining time.\n",
    "        0.83     = Validation score   (accuracy)\n",
    "        1.44s    = Training   runtime\n",
    "        0.02s    = Validation runtime\n",
    "\n",
    "......\n",
    "\n",
    "Fitting model: WeightedEnsemble_L2 ... Training model for up to 360.0s of the 556.21s of remaining time.\n",
    "        Ensemble Weights: {'LightGBMLarge': 0.4, 'NeuralNetTorch': 0.25, 'NeuralNetFastAI': 0.2, 'CatBoost': 0.15}\n",
    "        0.855    = Validation score   (accuracy)\n",
    "        0.16s    = Training   runtime\n",
    "        0.0s     = Validation runtime\n",
    "AutoGluon training complete, total runtime = 26.47s ... Best model: WeightedEnsemble_L2 | Estimated inference throughput: 2470.3 rows/s (200 batch size)\n",
    "TabularPredictor saved. To load, use: predictor = TabularPredictor.load(\"AutogluonModels/ag-20241119_214901\")\n",
    "Model training complete!\n",
    "INFO:root:It took 26.84 seconds training model. Time remaining: 555.67/600.00\n",
    "Prediction starts...\n",
    "Prediction complete! Outputs written to aga-output-20241119_214928.csv\n",
    "INFO:root:It took 0.15 seconds making predictions. Time remaining: 555.52/600.00\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "43e89033",
   "metadata": {},
   "source": [
    "You can override specific settings in the YAML configuration defined in the [config folder](https://github.com/autogluon/autogluon-assistant/tree/main/src/autogluon/assistant/configs) using\n",
    "the `config_overrides` parameter with format `\"key1=value1, key2.nested=value2\"` from the command line.\n",
    "\n",
    "\n",
    "Here are some example commands on using configuration overrides:\n",
    "\n",
    "```bash\n",
    "aga run toy_data --config_overrides \"feature_transformers.enabled_models=None, time_limit=3600\"\n",
    "\n",
    "# OR\n",
    "\n",
    "aga run toy_data --config_overrides \"feature_transformers.enabled_models=None\" --config_overrides \"time_limit=3600\"\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ff4c018",
   "metadata": {},
   "source": [
    "## Using AutoGluon Assistant (through Python Programming)\n",
    "\n",
    "Let's also look at how to use AutoGluon Assistant programmatically in Python:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "362ff589bb29d77d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from autogluon.assistant import run_assistant\n",
    "\n",
    "# Run the assistant\n",
    "output_file = run_assistant(task_path=\"./toy_data\", presets=\"medium_quality\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1a0c76c9931ef02a",
   "metadata": {},
   "source": [
    "Let's examine the predictions:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "cd40a5f7",
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "predictions = pd.read_csv(output_file)\n",
    "print(\"\\nFirst few predictions:\")\n",
    "print(predictions.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "52a7d48a",
   "metadata": {
    "code_font_size": "6pt"
   },
   "source": [
    "```\n",
    "First few predictions:\n",
    "   Transported\n",
    "0         True\n",
    "1        False\n",
    "2         True\n",
    "3         True\n",
    "4         True\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a1b543bf",
   "metadata": {},
   "source": [
    "## Using AutoGluon Assistant (via Web UI)\n",
    "\n",
    "AutoGluon Assistant Web UI allows users to leverage the capabilities of AG-A through an intuitive web interface.\n",
    "\n",
    "The web UI enables users to upload datasets, configure AG-A runs with customized settings, preview data, monitor execution progress, view and download results, and supports secure, isolated sessions for concurrent users."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e50f3025",
   "metadata": {},
   "source": [
    "### To run the AG-A Web UI:\n",
    "\n",
    "```bash\n",
    "aga ui\n",
    "\n",
    "# OR\n",
    "\n",
    "# Launch Web-UI on specific port e.g. 8888\n",
    "aga ui --port 8888\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b20d780a",
   "metadata": {},
   "source": [
    "AG-A Web UI should now be accessible in your web browser at http://localhost:8501 or the specified port.\n",
    "\n",
    "*Note: It might take up to a few mins to launch webui for the first time, since we are downloading the sample datasets...*"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "79eb2f75ce0e5eed",
   "metadata": {
    "id": "I-da0PXvpD96"
   },
   "source": [
    "## Conclusion\n",
    "\n",
    "In this quickstart tutorial, we saw how AutoGluon Assistant simplifies the entire ML pipeline by allowing users to solve machine learning problems with minimal efforts. With just a data directory, AutoGluon Assistant handles the entire process from data understanding to prediction generation. Check out the other tutorials (WIP) to learn more about customizing the configuration, using different LLM providers, and handling various types of ML tasks.\n",
    "\n",
    "Want to dive deeper? Explore our GitHub repository for more advanced features and examples."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
